#include "nodemodel.h"
#include "../hierarchyframe.h"
#include "../../core/resources/resourcemodel.h"
#include "../../core/resources/resourceeffect.h"
#include "../../core/resources/resourcemanager.h"
#include "../../core/resources/resourceeffectvariables.h"
#include "../../core/resources/resourceactivetechnique.h"

namespace gunsmith
{
CNodeModel::CNodeModel(CResourceModel *pResModel, CResourceEffect *pResEffect) : CNodeWorldObject(NT_MODEL)
{
	m_pDevice				= m_pResMngr->GetDevice();
	m_pResModel				= pResModel;
	m_pResEffect			= pResEffect;
	m_pFrameRoot			= pResModel->GetFrameRoot();
	m_pAnimCtrl				= pResModel->GetAnimControl();
	m_pEffect				= pResEffect->GetEffect();
	m_pResEVars				= m_pResMngr->GetResourcesByName<CResourceEffectVariables>(RID_EFFECT_VARIABLES);
	m_pResActiveTechnique	= m_pResMngr->GetResourcesByName<CResourceActiveTechnique>(RID_ACTIVE_TECHNIQUE);

	SetupFrameMatrices(m_pFrameRoot);
}

CNodeModel::~CNodeModel()
{
}

/*!	\brief		Draws the frames starting at root frame. (see remarks)
	\return		void
	\remarks	Invokes DrawFrames().
	\sa			*/
void CNodeModel::Draw()
{
	/*! \todo make this more generic by cycling through a list of required effect variables retrieve them and assign them */
	// unsafe cause resource could have been not created due to any reason and is empty, but i know it's not ;)
	const TEffectVariable* pViewVar = m_pResEVars->GetVar("CameraViewMatrix");
	const TEffectVariable* pProjVar = m_pResEVars->GetVar("CameraProjectionMatrix");

	// set active technique
	HRESULT hr = m_pEffect->SetTechnique(m_pResActiveTechnique->GetActive().c_str());
	
	// set matrices
	m_pEffect->SetMatrix("gWorldMatrix", &m_matWorldTransformation);
	m_pEffect->SetMatrix("gViewMatrix", (const D3DXMATRIX*)pViewVar->pData);
	m_pEffect->SetMatrix("gProjectionMatrix", (const D3DXMATRIX*)pProjVar->pData);
	DrawFrames(m_pFrameRoot);
}

/*!	\brief		If the scene graph enabled debug draw this function will be called instead of Draw().
	\return		void
	\remarks	
	\sa			*/
void CNodeModel::DrawDebug()
{
	DrawBoundingBox(m_pFrameRoot);
}

/*!	\brief		Actual draw method which recursivly draws its frame hierarchy. (see remarks) 
	\param		pFrameBase
	\return		void
	\remarks	Calculates bone matrices, sets effect variables aswell as invoking effect->Begin() and effect->End().
	\sa			*/
void CNodeModel::DrawFrames(D3DXFRAME *pFrameBase)
{
	TMeshContainer *pContainer = (TMeshContainer*)pFrameBase->pMeshContainer;
	
	if(pContainer && pContainer->pSkinInfo && m_pEffect)
	{
		D3DXBONECOMBINATION *pBoneCombinationArray = (D3DXBONECOMBINATION*)pContainer->pBoneCombBuf->GetBufferPointer();

		// travel attributes and retrieve matrices accordingly
		for(DWORD iAttr = 0; iAttr < pContainer->iNumAttrGroups; iAttr++)
		{
			CreateBoneMatrices(pBoneCombinationArray, iAttr, pContainer);
			// set important bone information
			m_pEffect->SetMatrixArray	("gBoneMatrices",	pContainer->pBoneMatrices, pContainer->iNumPalettes);
			m_pEffect->SetInt			("gNumInfluences",	pContainer->iMaxInfluences);

			DrawMesh(pContainer, iAttr);
		}
	}

	if(pFrameBase->pFrameSibling)
		DrawFrames(pFrameBase->pFrameSibling);

	if(pFrameBase->pFrameFirstChild)
		DrawFrames(pFrameBase->pFrameFirstChild);
}

/*!	\brief		Sets up the frame matrices and retrieves pointers to the combined transformation matrices.
	\param		pBaseFrame	Base frame.
	\return		void
	\remarks	
	\sa			*/
void CNodeModel::SetupFrameMatrices(D3DXFRAME *pFrameBase)
{
	TMeshContainer *pContainer = (TMeshContainer*)pFrameBase->pMeshContainer;

	if(pContainer)
	{
		for(DWORD i = 0; i < pContainer->iNumBones; ++i)
		{
			THierarchyFrame *pCurrentFrame = (THierarchyFrame*)D3DXFrameFind(m_pFrameRoot, pContainer->pSkinInfo->GetBoneName(i));
			pContainer->ppCombinedMatrices[i] = &pCurrentFrame->CombinedTransformationMatrix;
		}
	}

	if(pFrameBase->pFrameSibling)
		SetupFrameMatrices(pFrameBase->pFrameSibling);
	if(pFrameBase->pFrameFirstChild)
		SetupFrameMatrices(pFrameBase->pFrameFirstChild);
}

/*!	\brief		Updates frames matrices according to time.
	\param		pFrameBase		Base frame.
	\param		pParentMatrix	Paren matrix.
	\return		void
	\remarks	
	\sa			*/
void CNodeModel::UpdateFrameMatrices(D3DXFRAME *pFrameBase, D3DXMATRIX *pMatrixParent)
{
	THierarchyFrame *pFrame = (THierarchyFrame*)pFrameBase;
	
	if(pMatrixParent)
		D3DXMatrixMultiply(&pFrame->CombinedTransformationMatrix, &pFrame->TransformationMatrix, pMatrixParent);
	else
		pFrame->CombinedTransformationMatrix = pFrame->TransformationMatrix;

	if(pFrame->pFrameSibling)
		UpdateFrameMatrices(pFrame->pFrameSibling, pMatrixParent);

	if(pFrame->pFrameFirstChild)
		UpdateFrameMatrices(pFrame->pFrameFirstChild, &pFrame->CombinedTransformationMatrix);
}

/*!	\brief		Updates model according to delta time. (see remarks)
	\param		fDelta	Delta time.
	\param		pData	Additional data.
	\return		void
	\remarks	Invokes AnimCtrl->AdvanceTime().
	\sa			*/
void CNodeModel::Update(float fDelta, void *pData)
{
	__super::Update(fDelta);

	if(m_pAnimCtrl)
		m_pAnimCtrl->AdvanceTime(fDelta, NULL);

	UpdateFrameMatrices(m_pFrameRoot, NULL);
}

/*!	\brief		
	\param		pFrameBase
	\return		void
	\remarks	
	\sa			*/
void CNodeModel::DrawBoundingBox(const D3DXFRAME *pFrameBase)
{
	if(!pFrameBase)
		return;

	TMeshContainer *pContainer = (TMeshContainer*)pFrameBase->pMeshContainer;

	if(pContainer)
	{
		D3DXMATRIX matIdentity;
		D3DXMatrixIdentity(&matIdentity);
		m_pDevice->SetTransform(D3DTS_WORLD, &matIdentity);//&m_matWorldTransformation);
		m_pDevice->SetStreamSource(0, pContainer->pVBAabb, 0, pContainer->iStrideAabb);
		m_pDevice->SetIndices(pContainer->pIBAabb);
		m_pDevice->SetVertexDeclaration(pContainer->pVDAabb);
		m_pDevice->DrawIndexedPrimitive(D3DPT_LINELIST, 0, 0, 8, 0, 12);
		m_pDevice->SetRenderState(D3DRS_LIGHTING, false);
	}

	if(pFrameBase->pFrameSibling)
		DrawBoundingBox(pFrameBase->pFrameSibling);

	if(pFrameBase->pFrameFirstChild)
		DrawBoundingBox(pFrameBase->pFrameFirstChild);
}

void CNodeModel::CreateBoneMatrices(D3DXBONECOMBINATION *pBoneCombinationArray, DWORD iAttr, TMeshContainer* pContainer)
{
	for(DWORD iPaletteId = 0; iPaletteId < pContainer->iNumPalettes; ++iPaletteId)
	{
		UINT iIndex = pBoneCombinationArray[iAttr].BoneId[iPaletteId];
		if(iIndex != UINT_MAX)
		{
			// fill final bone matrices
			D3DXMatrixMultiply(&pContainer->pBoneMatrices[iPaletteId],
							   &pContainer->pBoneOffsetMatrices[iIndex],
							   pContainer->ppCombinedMatrices[iIndex]);
		}
	}
}

void CNodeModel::DrawMesh(TMeshContainer *pContainer, DWORD iAttr)
{
	UINT iPasses;
	//m_pEffect->SetTechnique(m_sTechniqueName);
	m_pEffect->Begin(&iPasses, D3DXFX_DONOTSAVESTATE);
	
	for(UINT i = 0; i < iPasses; ++i)
	{
		m_pEffect->BeginPass(i);
			pContainer->pSkinnedMesh->DrawSubset(iAttr);
		m_pEffect->EndPass();
	}

	m_pEffect->End();
}
};